// EpollServer.hpp
#include <cassert>
#include <iostream>
#include "Epoller.hpp"
#include <sys/epoll.h>
#include <string>
#include "err.hpp"
#include "log.hpp"
#include "Sock.hpp"
#include <unordered_map>
#include <functional>
#include "Util.hpp"
#include "Protocol.hpp"
using namespace std;
static const int N = 128;
struct Connection;
class EpollServer;

using call_back_t = function<void(Connection *)>;       // 用于Connection类中的回调函数
using func_t = function<void(Connection *, Request &)>; // 业务处理类型，带了Connection就可以在业务处理中调用Writer回调函数

struct Connection
{
public:
    Connection(const int fd, const string &clientip, const uint16_t clientport)
        : _fd(fd), _clientip(clientip), _clientport(clientport)
    {
    }

    // 注册事件就绪后调用的回调函数
    void Register(call_back_t reader, call_back_t writer, call_back_t excepter)
    {
        _reader = reader;
        _writer = writer;
        _excepter = excepter;
    }

    // IO信息
    int _fd;
    string _inbuffer;
    string _outbuffer;

    // 用户信息，在经过调整代码，将就绪事件都分开后，通过Connection对象即可了解到客户端是谁
    string _clientip;
    uint16_t _clientport;

    // IO处理函数
    call_back_t _reader; // 通过统一的成员变量，对于listen和read的调用方法不同
    call_back_t _writer;
    call_back_t _excepter;

    // 需要关心的事件
    int _events; // 主要是为了能够代码即可以ET模式又可以LT模式

    // 回指指针->在业务处理时需要用到，调用Writer以及EnableWriterReader
    EpollServer *_ptr;
};
class EpollServer
{
public:
    EpollServer(func_t func, uint16_t port = 8888)
        : _func(func), _port(port)
    {
    }

    void Init()
    {
        _listensock.Socket();
        _listensock.Bind(_port);
        _listensock.Listen();
        _epoller.Create();
        // 直接调用添加想要关心的事件即可
        AddConnection(_listensock.GetSock(), EPOLLIN | EPOLLET);
        LogMessage(Debug, "listensock adds successful");
    }

    // 事件派发器
    // 事件派发器）是一个对事件进行监听的对象，简单来说就是为事件写回调函数，当触发一个事件执行后，会执行为该事件绑定的回调函数。
    void Dispatcher()
    {
        int timeout = -1; // 阻塞
        while (true)
        {
            LoopOnce(timeout);
        }
    }

    // 循环一次
    void LoopOnce(int timeout)
    {
        LogMessage(Debug, "Loop is starting");
        // 监听事件，等待就绪事件
        int n = _epoller.Wait(eparray, N, timeout); // 不需要对n进行判断了，n<0或n==0，都不会执行下面的循环操作
        // n是就绪事件的个数，只需要循环执行n次即可，因为所有拷贝的就绪事件是从左向右连续有效的
        for (int i = 0; i < n; i++)
        {
            int events = eparray[i].events;
            int fd = eparray[i].data.fd;
            // 我们将所有的异常情况，最后全部转化成为recv，send的异常，能够使代码更加清楚
            // 让所有的except都去reader和writer中处理，因为fd既然是异常了，那么在reader和
            // writer中肯定也是无法正常读取，然后进入到excepter中处理
            if ((eparray[i].events & EPOLLERR) || (eparray[i].events & EPOLLHUP))
            {
                events |= (EPOLLIN | EPOLLOUT);
            }
            // 有了_connections后，我们可以基本unorder_map的kv特性，通过fd找到他自己的Connection对象
            // 由于注册时的给予的回调函数不同，因此accept和read会执行各自的_reader函数
            if ((events & EPOLLIN) && ConExist(fd))
            {
                _connections[fd]->_reader(_connections[fd]);
            }
            if ((events & EPOLLOUT) && ConExist(fd))
            {
                _connections[fd]->_writer(_connections[fd]);
            }
        }
    }

    // 用于保证fd的有效性，因为我们将异常全部交给writer和reader处理了，那么在执行
    // if ((events & EPOLLOUT))时，fd已经被excepter函数从_connections中移除了，就不能执行了
    // 因此安全起见，都加上了ConExist
    bool ConExist(int fd)
    {
        return _connections.find(fd) != _connections.end();
    }

    // 将所有的添加操作都使用一个函数来解决
    void AddConnection(const int fd, int events, const uint16_t clientport = 8888, const string &clientip = "127.0.0.1")
    {
        // 1. 设置fd是非阻塞
        // 使用判断能够保证即使是修改代码为LT模式，也能正常使用此函数
        if (events & EPOLLET)
            Util::NonBlock(fd); // ET工作模式需要循环读取，保证不会因为read和write时会阻塞住
        // 2. 构建connection对象，交给connections_来进行管理
        Connection *con = new Connection(fd, clientip, clientport);
        // 注意这边的写法，如果是listen有链接到来，给予的回调函数是Accepter,其余的writer，excepter不重要
        if (fd == _listensock.GetSock())
        {
            // con->_reader = bind(&EpollServer::Accepter, this, placeholders::_1);
            con->Register(bind(&EpollServer::Accepter, this, placeholders::_1), nullptr, nullptr);
        }
        // 其余的都是网络通信套接字，就可以默认都是Reader，Writer，Excepter，调用时会根据不同的就绪事件来选择
        else
        {
            con->Register(bind(&EpollServer::Reader, this, placeholders::_1),
                          bind(&EpollServer::Writer, this, placeholders::_1),
                          bind(&EpollServer::Excepter, this, placeholders::_1));
            // con->_reader = bind(&EpollServer::Reader, this, placeholders::_1);
            // con->_writer = bind(&EpollServer::Writer, this, placeholders::_1);
            // con->_excepter = bind(&EpollServer::Excepter, this, placeholders::_1);
        }
        con->_events = events;
        con->_ptr = this;
        _connections.insert(make_pair(fd, con));
        // 3.fd && events 写透到内核中
        _epoller.AddModEvent(fd, events, EPOLL_CTL_ADD);
        LogMessage(Debug, "AddConnection is done....");
    }

    // 增加或修改fd想要关心的事件
    void EnableReaderWriter(int fd, bool reader, bool writer)
    {
        int events = 0;
        events |= ((reader ? EPOLLIN : 0) | (writer ? EPOLLOUT : 0) | EPOLLET);
        _epoller.AddModEvent(fd, events, EPOLL_CTL_MOD);
        // 关于 EPOLL_CTL_MOD 操作，它是用于修改一个已经注册的文件描述符的监听事件。
        // 这里的“修改”是指对指定文件描述符的监听事件进行整体替换，而不是增量式地添加或删除某些事件。
        // 具体来说，当你使用 EPOLL_CTL_MOD 时，你提供的 event 参数中的事件将完全覆盖之前为该文件描述符设置的监听事件。
    }
    // 连接管理器
    void Accepter(Connection *con)
    {
        // 由于ET模式在有链接到来时只会通知一次，因此要循环读取，保证不会有遗漏
        do
        {
            string clientip;
            uint16_t clientport;
            int errcode; // 错误码
            // 1. 新连接到来
            int sock = _listensock.Accept(clientip, clientport, errcode);
            if (sock > 0)
            {
                LogMessage(Debug, "%s:%d已经连上服务器了", clientip.c_str(), clientport);
                // 1.1 此时在这里，我们能不能进行recv/read ? 不能，只有epoll知道sock上面的事件情况，将sock添加到epoll中
                AddConnection(sock, EPOLLIN | EPOLLET, clientport, clientip);
            }
            else
            {
                // 当错误码是这个时，说明底层暂时没有连接了
                if ((errcode == EWOULDBLOCK) || (errcode == EAGAIN))
                {
                    break;
                }
                else if (errcode == EINTR)
                {
                    continue;
                }
                else
                {
                    // accept返回错误时不能调用except，还是要继续监听
                    LogMessage(Warning, "Listensock error,errno:%d,errorstring:%s", errno, strerror(errno));
                    continue;
                }
            }

        } while (con->_events & EPOLLET);
        // 之所以要使用do while（con->_events&EPOLLET）是因为这样能够保证如果是ET模式，就会不停地循环，
        // 假设又进行改变了代码，变成了LT模式，也只会执行一次循环，正常工作
        LogMessage(Debug, "Accepter is done...");
    }

    void Reader(Connection *con)
    {
        // 读取完毕本轮数据！
        int fd = con->_fd;
        do
        {
            // 使用应用层协议来保证读到的是完整报文，并且有自己独立的缓冲区来存储读到的数据（保证第二次读取时不会覆盖，
            // 也保证循环读取后不是完整报文，下次再来新的报文时，之前读取的部分报文还是存在的）
            char buff[1024];
            ssize_t n = read(fd, buff, sizeof(buff) - 1);
            if (n > 0)
            {
                buff[n] = 0;
                con->_inbuffer += buff;
                string requeststr;
                // 根据基本协议，进行数据分析
                int n = Parse(con->_inbuffer, requeststr);
                if (n > 0)
                {
                    // 去报头
                    requeststr = DelHeader(requeststr, n);
                    Request req;
                    // 反序列化
                    req.Deserialization(requeststr);
                    // 业务处理
                    _func(con, req); // request 保证是一个完整的请求报文！
                }
            }
            else if (n == 0)
            {
                // 对方想要关闭链接了
                con->_excepter(con);
                break;
            }
            else
            {
                // EAGAIN==EWOULDBLOCK,提示进程现在没有数据可读请稍后再试
                if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
                {
                    break;
                }
                // 遭遇信号中断
                else if (errno == EINTR)
                {
                    continue;
                }
                else
                {
                    // 这时是真正的出错了，调用异常回调函数
                    con->_excepter(con);
                    break;
                }
            }

        } while (con->_events & EPOLLET); // 更好地兼容LT工作模式
        LogMessage(Debug, "Reader is done...");
    }

    void Writer(Connection *con)
    {
        do
        {
            int fd = con->_fd;
            // 写
            ssize_t n = write(fd, (con->_outbuffer).c_str(), (con->_outbuffer).size());
            //!!!这里要注意，写入的时候也会有可能没有办法将数据全部写入，底层的输出缓冲区可能空间不够
            // 这就会导致write会进行阻塞，我们因此也是将它设置成了非阻塞。假设底层还剩余的空间是200字节，
            // 发送了一个1000字节，就会直接“出错”，errno是EWOULDBLOCK，即文件描述符是非阻塞的，但资源暂时不可用
            if (n > 0)
            {
                con->_outbuffer.erase(0, n);   // 删除掉以及写入的个数
                if ((con->_outbuffer).empty()) // 如果缓冲区不为空就说明底层空间不够，只写了一部分
                {
                    EnableReaderWriter(fd, true, false);
                    break; // 写完后就跳出循环
                }
                else
                {
                    EnableReaderWriter(fd, true, true); // 再次设置fd的写事件
                }
            }
            else //<=0
            {
                // 如果底层的发送缓冲区已满，无法继续接受更多数据，系统就会返回EAGAIN错误
                if ((errno == EWOULDBLOCK) || (errno == EAGAIN))
                {
                    break; // 只能下次就绪(底层缓冲区有空间)再写
                }
                else if (errno == EINTR)
                {
                    continue;
                }
                else
                {
                    // 剩下的就是异常了
                    con->_excepter(con);
                    break;
                }
            }
        } while (con->_events & EPOLLET);
        LogMessage(Debug, "Writer is done...");
    }

    void Excepter(Connection *con)
    {
    }

private:
    uint16_t _port;
    Sock _listensock;
    Epoller _epoller;
    struct epoll_event eparray[N]; // 接收就绪事件时使用
    // 主要是为了使每个通信fd都能够拥有自己的接受缓冲区（即使一次没有读完，至少保存了这次读取的内容）来保证我们收到的数据是完整的
    unordered_map<int, Connection *> _connections;
    func_t _func; // 网络业务处理
};